﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using BeanboxSoftware.BeanProxy.Internals;

namespace BeanboxSoftware.BeanProxy
{
  /// <summary>
  /// Used to create proxies and get method configurations
  /// </summary>
  public static partial class ProxyManager
  {
    /// <summary>
    /// Returns an event config for an event named <paramref name="eventName"/> from a proxy 
    /// </summary>
    /// <typeparam name="T">Delegate type of the event</typeparam>
    /// <param name="proxy">proxy built by ProxyManager</param>
    /// <param name="eventName">name of the event</param>
    /// <returns>event access</returns>
    public static EventAccess<T> LookupEvent<T>(object proxy, string eventName)
    {
      var iprox = GetIProxy(proxy);
      var ei = iprox.Builder.AllEvents().ToList().Find(e => e.Name == eventName, null);

      if (ei == null)
      {
        throw new KeyNotFoundException(string.Format("No event names {0} found in {1}", eventName, proxy.GetType().Name));
      }

      var ea = new EventAccess(iprox, ei);
      VerifySignature(iprox, ea, MethodConfig.GetInvoker<T>());

      var eat = new EventAccess<T>(ea);
      return eat;
    }

    private static void VerifySignature(IProxy iprox, EventAccess ea, MethodInfo requestedSig)
    {
      var existing = new MethodDefinition(ea.RaiseMethod) { Name = ea.Name };
      var requested = new MethodDefinition(requestedSig) { Name = ea.Name };

      if (!requested.Matches(existing))
      {
        throw new InvalidCastException(
          string.Format("The event {0} does not match the method signature requested {1}",
            ea.Name, requestedSig.ToString()));
      }
    }

    public static void VerifyCalls(object proxy)
    {
      IProxy iprox = GetIProxy(proxy);

      iprox.MethodGroup.Verify();
    }

    public static void AllowExpectedCallsOnly(object proxy, bool allowExpectedOnly)
    {
      IProxy iprox = GetIProxy(proxy);
      iprox.MethodGroup.Strict = allowExpectedOnly;
    }
  }

  /// <summary>
  /// Very similar in purpose to MethodConfig, but used to control events on a proxy
  /// </summary>
  public class EventAccess
  {
    /// <summary>
    /// Returns a method info for the remove method which removes delegates from the event
    /// </summary>
    public MethodInfo RaiseMethod { get; protected set; }
    /// <summary>
    /// Returns a method info for the add method which adds delegates to the event
    /// </summary>
    public MethodInfo AddMethod { get; protected set; }
    /// <summary>
    /// Return the method info for the raise method for the event. Typically this is only needed publically to
    /// verify the event signature
    /// </summary>
    public MethodInfo RemoveMethod { get; protected set; }
    /// <summary>
    /// Returns the target object that owns the event
    /// </summary>
    public IProxy Target { get; protected set; }

    /// <summary>
    /// Event name
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Build an EventAccess with a known proxy and event name
    /// </summary>
    /// <param name="iprox">IProxy are built by ProxyManager</param>
    /// <param name="eventName">event name</param>
    public EventAccess(IProxy iprox, EventInfo ei)
    {
      Name = ei.Name;
      Target = iprox;

      Type type = iprox.GetType();

      RaiseMethod = type.GetMethod(BuilderBase.RaiseEventPrefix + ei.Name);

      AddMethod = ei.GetAddMethod() ?? ei.GetAddMethod(true);
      RemoveMethod = ei.GetRemoveMethod() ?? ei.GetRemoveMethod(true);
    }

    /// <summary>
    /// Raises the event
    /// </summary>
    /// <param name="args">args to pass to event</param>
    /// <returns>event result</returns>
    public object Fire(params object[] args)
    {
      return RaiseMethod.Invoke(Target, args);
    }

    /// <summary>
    /// Adds a delegate listener to the event
    /// </summary>
    /// <param name="listener">delegate listener</param>
    public void Add(Delegate listener)
    {
      AddMethod.Invoke(Target, new object[] { listener });
    }

    /// <summary>
    /// Removes a delegate listener from the event
    /// </summary>
    /// <param name="listener">delegate listener</param>
    public void Remove(Delegate listener)
    {
      RemoveMethod.Invoke(Target, new object[] { listener });
    }
  }

  public class EventAccess<T>
  {
    public EventAccess Access { get; protected set; }

    public EventAccess(EventAccess ea)
    {
      Access = ea;
      var del = Delegate.CreateDelegate(typeof(T), ea.Target, ea.RaiseMethod.Name, false);
      var obj = del as object;
      Fire = (T)obj;
    }

    public T Fire
    {
      get;
      protected set;
    }

    /// <summary>
    /// Adds the delegate listener to the event
    /// </summary>
    /// <param name="action">delegate listener</param>
    public virtual void Add(T action)
    {
      Access.Add((Delegate)((object)action));
    }

    /// <summary>
    /// Removes the delegate listener from the event
    /// </summary>
    /// <param name="action">delegate listener</param>
    public virtual void Remove(T action)
    {
      Access.Remove((Delegate)((object)action));
    }
  }
}
